package environment;

import individual.Pindividual;



import java.util.Random;
import java.util.Date;

import problems.Problem;
import problems.TSP;
import utils.LinkedList;
import utils.ListIterator;
import utils.Node;



/** keeps all the mutexes and variables */

public class Environment extends Thread{
	
	private static final int maxPopulation = 10000;	
	private LinkedList<Pindividual> pindividuals;
	private static final int initialPopulation = 2;
	
	private static Object lock = new Object();

	private static double[] scores;
	
	private Environment() {				
		pindividuals = new LinkedList<Pindividual>(); //pindividuals = new LinkedList<Pindividual>();
//		pindividuals.ensureCapacity(maxPopulation);

		scores = new double[maxPopulation];

		/** create initial population */		
		for(int i = 0; i < initialPopulation ; i++ ) {
			Pindividual newGuy = Pindividual.createNatural( Thread.NORM_PRIORITY, 4000 );
			newGuy.container = pindividuals.addLast( newGuy );
		}
	}

	public static void begin() {
		
		
		
		
		getInstance().setPriority(Thread.MIN_PRIORITY);
		getInstance().start();
	}
		
	public void run() {		
		/** start each pindividual */
		System.out.println("starting");
		
		synchronized (lock) {
			ListIterator<Pindividual> i = pindividuals.listIterator();
			if(i!=null)
			while(i.hasNext())
			{
				((Node<Pindividual>)(i.next())).data.start();
			}
		}
			
		boolean done = false;
		System.out.println("running");
		int counter = 0;

		Date last_time = new Date();
		boolean reset_time  = false;

		while(!done)
		{	
			//temp
			Date cur_time = new Date();

			if(cur_time.getTime() - last_time.getTime() >= 1000)
				reset_time = true;

			synchronized (lock) {
				ListIterator<Pindividual> iter = pindividuals.listIterator();
				
				/** evaluate **/
				double best = Double.MIN_VALUE;
				double worst = Double.MAX_VALUE;
				double avg = 0.0;
				float avgPriority = 0.0f;
				int lowestPriority = Thread.MAX_PRIORITY;
				int highestPriority = Thread.MIN_PRIORITY;
				int numActive = 0;
				
				while(iter.hasNext())
				{
					Pindividual pindividual = ((Node<Pindividual>) iter.next()).data;
					SingletonHolder.problem.eval( pindividual );
									
					if(best < pindividual.score)
						best = pindividual.score;
					if(worst > pindividual.score)
						worst = pindividual.score;

					int priority = pindividual.getPriority();

					if(highestPriority < priority)
						highestPriority = priority;
					if(lowestPriority > priority)
						lowestPriority = priority;
				
					if(reset_time)
					if(Randomizer.flipCoin(0.0005))
					{
						if(Randomizer.flipCoin(0.2))
							kill(pindividual);
						else
							mutate(pindividual,0.1);
					}
					
					avg += pindividual.score;
					avgPriority += priority;
					
					if(pindividual.started)
						numActive++;
				}	
	
				/** calculate average priority */
				if(pindividuals.size()>0)
				{
					avgPriority /= pindividuals.size();
					avg /= pindividuals.size();
				}

				iter = pindividuals.listIterator();

				System.out.println("popSize:"+getInstance().pindividuals.size() + "(" + numActive + ")" +" $:(" + worst + "~" + best + "(" + avg + ") P:(" + lowestPriority + "~" + highestPriority + "("+ avgPriority+")" );
				/** analyze */
				while(iter.hasNext())
				{
					Pindividual pindividual = ((Node<Pindividual>) iter.next()).data;					
					if(pindividual.terminate)
					{						
						removePindividual(pindividual);
						break;
					}
				}

				if(reset_time) 
				{
					
//					/** punish */	
//					if(Randomizer.flipCoin(0.1))
//					for(int i = 0; i < 0.2 * pindividuals.size(); i++)
//					{
//						Pindividual pindividual = selectBad();
//						if(Randomizer.flipCoin(0.5))
//						{
//							kill(pindividual);
//						}
//						else
//						{
//							mutate(pindividual, 0.5 );
//							punish(pindividual);
//						}
//					}
//
//					/** reward */
//					if(Randomizer.flipCoin(0.1))
//					for(int i = 0; i < 0.3 * pindividuals.size(); i++)
//					{
//						Pindividual pindividual = selectGood();
//						reward(pindividual);
//						pindividual.start();
//					}
					
					iter = pindividuals.listIterator();
					
					while(iter.hasNext())
					{
						Pindividual pindividual = ((Node<Pindividual>) iter.next()).data;
						if(pindividual.score < avg)
						{
							if(Randomizer.flipCoin(0.5))
							{
								kill(pindividual);
							}
							else
							{
								mutate(pindividual, 0.5 );
								punish(pindividual);
							}
						}
						else
						{
							reward(pindividual);
							pindividual.start();
						}
					}
					
					/** creation */										
					if(pindividuals.size() < 100)
					{
						
						Pindividual newGuy;
						if(Randomizer.flipCoin(0.8))
						{
							newGuy = Pindividual.createNatural(Thread.NORM_PRIORITY, 1000);
						} else {
							newGuy = Pindividual.createEmpty(Thread.NORM_PRIORITY, Randomizer.integer(2000)+1);
						}
						addPindividual(newGuy);
						newGuy.start();
					}
				}
				
			}

			if(reset_time)
			{
				last_time = new Date();
				reset_time = false;
			}	

			/** mutate/kill/create **/
			/** mutation rate are very high for low scored individual (>50%)**/
		}		
		System.out.println("finishing");
	} 

//	private Pindividual selectBad()
//	{
//		return _tournament(16, false);
//	}
//
//	private Pindividual selectGood()
//	{
//		return _tournament(16, true);
//	}
//
//	private Pindividual _tournament(int tsize, boolean better)
//	{
//		synchronized(lock) {
//			if(tsize <= 1) {
//				return (Pindividual)pindividuals.get( Randomizer.integer( pindividuals.size() ));
//			}
//		
//			Pindividual p1 = _tournament((int)(tsize/2), better);
//			Pindividual p2 = _tournament((int)(tsize/2), better);
//
//			if(better)
//				return p1.score > p2.score ? p1 : p2;
//			else
//				return p1.score < p2.score ? p1 : p2;
//		}
//	}
	
	private void moveLeft(Pindividual p)
	{
		
	}
	
	private static void kill(Pindividual p)
	{
		p.terminate = true;
	
	}
	
	private static void mutate(Pindividual p, double prob)
	{
		p.codeMemory.mutate(prob);
	}
	
	private static void punish(Pindividual p)
	{		
		p.decreasePriority();
	}
	
	private static void reward(Pindividual p)
	{
		p.increasePriority();		
	}
		
	public static Pindividual getNeighbor(Pindividual from, int position)
	{
		synchronized (lock) {
			ListIterator<Pindividual> iter = new ListIterator<Pindividual>(from.container);
			
			if(position >= 0)
			{
				position = position % getInstance().pindividuals.size(); 
				for(int i = 0; i < position; i++) {
					iter.next();
				}
				return ((Node<Pindividual>) iter.next()).data;				
			}
			else
			{
				position = position % getInstance().pindividuals.size(); 
				for(int i = 0; i > position; i--) {
					iter.prev();
				}
				return ((Node<Pindividual>) iter.prev()).data;
			}
		}
	}
	
	public static int addPindividual(Pindividual i)
	{
		synchronized (lock) {
			if(getInstance().pindividuals.size() < maxPopulation)
			{
				i.container = getInstance().pindividuals.addLast( i );
				return 1;
			}
			else
			{
				return 0;
			}	
		}		
	}
	
	public static int addPindividual(Pindividual i, Pindividual nextItem)
	{
		synchronized (lock) {
			if(getInstance().pindividuals.size() < maxPopulation)
			{

				i.container = getInstance().pindividuals.insertPrev(nextItem.container, i);
				return 1;

			}
			else
			{
				return 0;
			}	
		}
		
	}
	
	public static int removePindividual(Pindividual i)
	{
		synchronized (lock) {
			if(getInstance().pindividuals.size()>0)
			{
				getInstance().pindividuals.remove(i.container);
				return 1;
			}
			return 0;	
		}
	}
	
	private static class SingletonHolder { 
		private static Problem problem;
		private static final Environment INSTANCE = new Environment();
	}	
	
	public static class Randomizer {
		private static final Random rand = new Random();
		public static boolean flipCoin(double p)
		{			
			return rand.nextDouble() < p;
		}
		public static double gaussian()
		{
			return rand.nextGaussian();
		}
		public static int integer()
		{
			return rand.nextInt();
		}
		public static double floating()
		{
			return rand.nextDouble();
		}
		public static int integer(int n)
		{
			return rand.nextInt(n);
		}
	}
 
	private static Environment getInstance() {
		return SingletonHolder.INSTANCE;
	}
	
	public static Environment init(Problem problem) {
		SingletonHolder.problem = problem;
		return SingletonHolder.INSTANCE;
	}
		
	

}
